package com.jia.coupons.service.impl;

import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.BooleanUtil;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jia.constant.RedisConstant;
import com.jia.coupons.constant.OrderRedisConstant;
import com.jia.coupons.entity.SeckillVoucher;
import com.jia.coupons.entity.VoucherOrder;
import com.jia.coupons.mapper.VoucherOrderMapper;
import com.jia.coupons.service.SeckillVoucherService;
import com.jia.coupons.service.VoucherOrderService;
import com.jia.sms.holder.UserHolder;
import com.jia.utils.RedisIdWorker;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.aop.framework.AopContext;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.Collections;
import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 * (VoucherOrder)表服务实现类
 *
 * @author makejava
 * @since 2024-10-06 23:43:09
 */
@Service("voucherOrderService")
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder>
        implements VoucherOrderService {

    @Resource
    private SeckillVoucherService seckillVoucherService;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private RedissonClient redissonClientOne;

    @Resource
    private RedissonClient redissonClientTwo;

    private RLock lock;

    private static final DefaultRedisScript<Long> UNLOCK_SCRIPT;

    static {
        UNLOCK_SCRIPT = new DefaultRedisScript<>();
        UNLOCK_SCRIPT.setLocation(new ClassPathResource("lua\\unlock.lua"));
        UNLOCK_SCRIPT.setResultType(Long.class);
    }

    public static final String REDIS_PREFIX = RedisConstant.PROJECT_REDIS_PREFIX
            + OrderRedisConstant.ORDER_PREFIX + RedisConstant.LOCK_PREFIX;

    public static final String ID_PREFIX = UUID.randomUUID().toString(true) + "-";

    /**
     * 新增数据
     *
     * @param voucherOrder 实例对象
     * @return 实例对象
     */
    @Override
    public VoucherOrder insert(VoucherOrder voucherOrder) {
        save(voucherOrder);
        return voucherOrder;
    }

    @Override
    // 单体服务
    public VoucherOrder insertOrder(Long voucherId) {
        // 当前时间
        Date date = new Date();
        // 查询优惠券信息
        SeckillVoucher seckillVoucher = seckillVoucherService.query()
                .eq("voucher_id", voucherId).one();
        Date beginTime = seckillVoucher.getBeginTime();
        if (beginTime.after(date)) {
            throw new RuntimeException("秒杀还未开始");
        }
        Date endTime = seckillVoucher.getEndTime();
        if (endTime.before(date)) {
            throw new RuntimeException("秒杀已结束");
        }
        Integer stock = seckillVoucher.getStock();
        if (stock <= 0) {
            throw new RuntimeException("秒杀库存不足");
        }

        Long userId = UserHolder.getUser().getId();
        // 单体服务常用锁机制
//        synchronized (userId.toString().intern()) {
//            // 事务提交完再释放锁
//            // 但是同一个类的方法 事务会失效
//            // 引入 aspectj
//            VoucherOrderService proxy =(VoucherOrderService) AopContext.currentProxy();
//            return proxy.createVoucherOrder(voucherId);
//        }

        // 分布式锁机制 Redis 实现 基础版本 会有问题 比如 其他线程会删除别人的锁 代码还没执行完 锁已经释放了
        String lockKey = REDIS_PREFIX + userId;
//
//        if (!lock(lockKey)) {
//            throw new RuntimeException("请不要重复下单");
//        }
//
//        try {
//            VoucherOrderService proxy = (VoucherOrderService) AopContext.currentProxy();
//            return proxy.createVoucherOrder(voucherId);
//        } finally {
//            unlock(lockKey);
//        }

        // 分布式锁机制 加强Redis 实现 增加value校验 因为判断和删除是两个操作 所以还是有原子性问题
//        if (!uuLock(lockKey)) {
//            throw new RuntimeException("请不要重复下单");
//        }
//
//        try {
//            VoucherOrderService proxy = (VoucherOrderService) AopContext.currentProxy();
//            return proxy.createVoucherOrder(voucherId);
//        } finally {
////            uuUnlock(lockKey);
//            // 分布式锁机制 加强redis lua脚本的使用 https://www.runoob.com/lua/lua-tutorial.html
//            luaUnlock(lockKey);
//        }

        // 分布式锁机制 redisson
        RLock lock1 = redissonClientOne.getLock(lockKey);
        RLock lock2 = redissonClientTwo.getLock(lockKey);

        // 创建联锁
        lock = redissonClientOne.getMultiLock(lock1,lock2);

        lock.lock();

        try {
            VoucherOrderService proxy = (VoucherOrderService) AopContext.currentProxy();
            return proxy.createVoucherOrder(voucherId);
        } finally {
            lock.unlock();
        }

    }

    // 加入事务 因为 synchronized块 只覆盖了部分代码 所以会出现
    // 线程执行完毕但是事务还没有提交 那其他线程就会进来 （新增还没有提交，新线程的查询就进来了）
    @Transactional
    public VoucherOrder createVoucherOrder(Long voucherId) {
        // synchronized 不推荐加在方法上 因为这样 所有用户都是串行执行
        // 当前用户
        Long userId = UserHolder.getUser().getId();
        // 因为是用户的线程安全问题所以只加在用户上
        // 因为每次进来的userId是不同的对象 所以使用toString()是相同的值
        // 但是toString() 每次会创建新对象 所以使用intern 直接读取常量池中的
//        synchronized (userId.toString().intern()) {
        // 加入一人一单逻辑
        Long count = query().eq("voucher_id", voucherId)
                .eq("user_id", userId).count();

        if (count > 0) {
            throw new RuntimeException("您已领取过该优惠券");
        }

        // 修改库存
        boolean success = seckillVoucherService.update().setSql("stock = stock - 1")
                .eq("voucher_id", voucherId)
                // 乐观锁 缺点 大量线程失败
//                .eq("stock", stock)
                .gt("stock", 0) // 把锁问题交给数据库
                .update();
        if (!success) {
            throw new RuntimeException("库存不足");
        }
        // 创建订单
        VoucherOrder voucherOrder = new VoucherOrder();
        // 订单id
        RedisIdWorker redisIdWorker = new RedisIdWorker(stringRedisTemplate);
        voucherOrder.setId(redisIdWorker.nextId("ORDER"));
        // 用户id
        voucherOrder.setUserId(userId);
        // 优惠券id
        voucherOrder.setVoucherId(voucherId);
        save(voucherOrder);
        return voucherOrder;
//        }

    }

    private boolean lock(String key) {
        Boolean b = stringRedisTemplate.opsForValue().setIfAbsent(key, "1",
                30, TimeUnit.SECONDS);
        return BooleanUtil.isTrue(b);
    }

    private void unlock(String key) {
        stringRedisTemplate.delete(key);
    }

    private boolean uuLock(String key) {
        String threadId = ID_PREFIX + Thread.currentThread().getId();
        Boolean b = stringRedisTemplate.opsForValue().setIfAbsent(key, threadId,
                30, TimeUnit.SECONDS);
        return BooleanUtil.isTrue(b);
    }

    private void uuUnlock(String key) {
        String threadId = ID_PREFIX + Thread.currentThread().getId();
        String value = stringRedisTemplate.opsForValue().get(key);
        if (StringUtils.isNotEmpty(value) & value.equals(threadId)) {
            stringRedisTemplate.delete(value);
        }
    }

    private void luaUnlock(String key) {
        stringRedisTemplate.execute(
                UNLOCK_SCRIPT,
                Collections.singletonList(key),
                ID_PREFIX + Thread.currentThread().getId());
    }
}
